﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace SharpShare.Afp.Protocol {
    internal sealed class AfpCache<TKey, TValue> {
        private Timer _evictTimer;
        private Dictionary<TKey, CacheEntry> _values = new Dictionary<TKey, CacheEntry>();
        private object _lock = new object();

        public AfpCache() {
            _evictTimer = new Timer(EvictTimerFired, null, 0, 1000);
        }

        public void Add(TKey key, TValue value, DateTime absoluteExpiration) {
            this.Add(key, value, absoluteExpiration, null);
        }

        public void Add(TKey key, TValue value, TimeSpan slidingExpiration) {
            this.Add(key, value, null, slidingExpiration);
        }

        private void Add(TKey key, TValue value, DateTime? absoluteExpiration, TimeSpan? slidingExpiration) {
            lock (_lock) {
                _values[key] = new CacheEntry() {
                    value = value,
                    absoluteExpiration = absoluteExpiration,
                    slidingExpiration = slidingExpiration
                };
            }
        }

        public bool TryRemove(TKey key, out TValue removedValue) {
            lock (_lock) {
                if (!_values.ContainsKey(key)) {
                    removedValue = default(TValue);
                    return false;
                }

                removedValue = _values[key].value;
                _values.Remove(key);
                return true;
            }
        }

        public bool ContainsKey(TKey key) {
            lock (_lock) {
                return _values.ContainsKey(key);
            }
        }
        public bool Remove(TKey key) {
            return _values.Remove(key);
        }

        public TValue this[TKey key] {
            get {
                lock (_lock) {
                    if (!_values.ContainsKey(key)) {
                        return default(TValue);
                    }

                    CacheEntry entry = _values[key];
                    entry.lastAccessed = DateTime.Now;
                    return entry.value;
                }
            }
        }

        private void EvictTimerFired(object state) {
            lock (_lock) {
                var expiredKeys = _values.Where(p => p.Value.IsExpired).Select(p => p.Key).ToList();
                expiredKeys.ForEach(k => _values.Remove(k));
            }
        }

        private sealed class CacheEntry {
            public TValue value;
            public DateTime? absoluteExpiration;
            public TimeSpan? slidingExpiration;
            public DateTime lastAccessed = DateTime.Now;

            public bool IsExpired {
                get {
                    if (absoluteExpiration.HasValue && DateTime.Now >= absoluteExpiration.Value) {
                        return true;
                    }

                    if (slidingExpiration.HasValue && (DateTime.Now - lastAccessed) >= slidingExpiration.Value) {
                        return true;
                    }

                    return false;
                }
            }
        }
    }
}
